package util

import (
	"archive/tar"
	"archive/zip"
	"bufio"
	"bytes"
	"compress/gzip"
	"context"
	"crypto/rand"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
	_ "net/http/pprof"
	"gitee.com/clearluo/gotools/net/utilhttp"

	"gitee.com/clearluo/gotools/zaplog"
	"github.com/shirou/gopsutil/process"

	"golang.org/x/text/encoding/simplifiedchinese"

	"gitee.com/clearluo/gotools/log"
)

var (
	processObj *process.Process
	httpObj    *http.Client
)

func init() {
	var err error
	processObj, err = process.NewProcess(int32(os.Getpid()))
	if err != nil {
		panic(err)
	}
	httpObj = utilhttp.NewHttpClient()
}
func randIntByCrypto() uint32 {
	var n uint32
	binary.Read(rand.Reader, binary.LittleEndian, &n)
	return n
}
func AssertMarshal(v interface{}, params ...int) string {
	b, _ := json.Marshal(v)
	preNum := 0
	if len(params) > 0 {
		preNum = params[0]
	}
	if preNum == 0 || len(b) <= preNum {
		return string(b)
	}
	return string(b[:preNum]) + "..."
}

func RandGenerate(n int) string {
	buf := make([]byte, n)
	rand.Read(buf)
	return hex.EncodeToString(buf)
}

// 获取整点以来的秒数
func GetSecondFromHour() int64 {
	return time.Now().Unix() % 3600
}

// 获取整天以来的秒数
func GetSecondFromDay() int64 {
	return time.Now().Unix() - GetSecondByDay00()
}

// 获取整周以来的秒数
func GetSecondFromWeek() int64 {
	day := int64(time.Now().Weekday())
	return 86400*(day-1) + GetSecondFromDay()
}

// 获取整月以来的秒数
func GetSecondFromMonth() int64 {
	day := int64(time.Now().Day())
	return 86400*(day-1) + GetSecondFromDay()
}

// 获取当前日期(20170802)零点对应的Unix时间戳
func GetSecondByDay00() int64 {
	timeStr := time.Now().Format("2006-01-02")
	//使用Parse 默认获取为UTC时区 需要获取本地时区 所以使用ParseInLocation
	t, _ := time.ParseInLocation("2006-01-02", timeStr, time.Local)
	return t.Unix()
}

// 获取当前日期前后n天对应的日期证书,0代表获取当前日期整数
func GetDateByN(n int) int64 {
	nTime := time.Now()
	yesTime := nTime.AddDate(0, 0, n)
	dayStr := yesTime.Format("20060102")
	day, _ := strconv.ParseInt(dayStr, 10, 64)
	return day
}

// 根据时间戳获取对应日期整数
func GetTDayByUnixTime(nowUnix int64) int64 {
	if nowUnix < 1 {
		return 0
	}
	tm := time.Unix(nowUnix, 0)
	nowDay, err := strconv.ParseInt(tm.Format("20060102"), 10, 64)
	if err != nil {
		fmt.Println(err)
		return 0
	}
	return nowDay
}

// 统计某函数执行时间
// 使用方式
// defer utils.Profiling("test")()
func Profiling(msg string) func() {
	start := time.Now()
	return func() {
		zaplog.Infof(fmt.Sprintf("%s[%s]:%s", msg, "use", time.Since(start)))
	}
}

// 判断文件夹是否存在
func PathExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

func GetFieldName(t reflect.Type) (map[string]string, error) {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	if t.Kind() != reflect.Struct {
		err := fmt.Errorf("Check type error not Struct")
		fmt.Println(err)
		fmt.Println(err)
		return nil, err
	}
	fieldNum := t.NumField()
	result := make(map[string]string, 0)
	for i := 0; i < fieldNum; i++ {
		result[t.Field(i).Tag.Get("json")] = t.Field(i).Type.Name()
	}
	return result, nil
}

type TplJson struct {
	Length int                      `json:"length"`
	Data   []map[string]interface{} `json:"data"`
}

func RunFuncName() string {
	pc := make([]uintptr, 1)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	return f.Name()
}
func randIntn(n int) int {
	return int(randIntByCrypto()) % n
}
func GetSignal() int {
	if randIntn(2) == 0 {
		return 1
	}
	return -1
}

// [min,max)
func RandMN(min int, max int) int {
	x := max - min
	n := randIntn(x) + min
	return n
}

func GetMysqlParam(n int) string {
	if n < 1 {
		return ""
	}
	str := "?"
	for i := 1; i < n; i++ {
		str += ",?"
	}
	return str
}

func GetIndexByWeigth(data []int) int {
	sum := 0
	index := 0
	for _, v := range data {
		sum += v
	}
	randN := randIntn(sum)
	sum = 0
	for i, v := range data {
		sum += v
		if randN < sum {
			index = i
			break
		}
	}
	return index
}

func GetPosInfo() (string, int) {
	_, file, line, ok := runtime.Caller(1)
	if !ok {
		return "unknow", 0
	}
	return filepath.Base(file), line
}

func GetPosInfoStr() string {
	_, file, line, ok := runtime.Caller(1)
	if !ok {
		return "[]"
	}
	return fmt.Sprintf("[%v:%v]\n", filepath.Base(file), line)
}

func FunFuncName() string {
	pc := make([]uintptr, 1)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	return f.Name()
}

type Charset string

const (
	UTF8    = Charset("UTF-8")
	GB18030 = Charset("GB18030")
	GBK     = Charset("GBK")
)

func readCommond(ctx context.Context, wg *sync.WaitGroup, std io.ReadCloser, buf *bytes.Buffer) {
	reader := bufio.NewReader(std)
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			return
		default:
			readString, err := reader.ReadString('\n')
			if err != nil || err == io.EOF {
				return
			}
			if runtime.GOOS == "windows" {
				readString = ConvertByte2String([]byte(readString), "GB18030")
			}
			buf.WriteString(readString)
			//fmt.Print(readString)
		}
	}
}

// 执行命令
func ExecCmd(dir string, str string) (string, error) {
	if dir == "" {
		dir = "./"
	}
	var out []byte
	var err error
	cmdSli := strings.Split(str, "|")
	for _, cmdStr := range cmdSli {
		cmdStr = strings.Trim(cmdStr, " ")
		param := strings.Split(cmdStr, " ")
		if len(param) < 1 {
			continue
		}
		cmd := exec.Command(param[0], param[1:]...)
		cmd.Dir = dir
		cmd.Stdin = bytes.NewBuffer(out)
		out, err = cmd.CombinedOutput()
		if err != nil {
			break
		}
	}
	return string(out), err
}

func ExecCmd2(ctx context.Context, runDir string, cmd string) (string, error) {
	var c *exec.Cmd
	if runtime.GOOS == "windows" {
		c = exec.CommandContext(ctx, "cmd", "/C", cmd) // windows
	} else {
		c = exec.CommandContext(ctx, "bash", "-c", cmd) // mac linux
	}
	c.Dir = runDir
	stdout, err := c.StdoutPipe()
	if err != nil {
		return "", err
	}
	stderr, err := c.StderrPipe()
	if err != nil {
		return "", err
	}
	var wg sync.WaitGroup
	// 因为有2个任务, 一个需要读取stderr 另一个需要读取stdout
	stdoutBuf := &bytes.Buffer{}
	stderrBuf := &bytes.Buffer{}
	wg.Add(2)
	go readCommond(ctx, &wg, stderr, stderrBuf)
	go readCommond(ctx, &wg, stdout, stdoutBuf)
	// 这里一定要用start,而不是run 详情请看下面的图
	err = c.Start()
	if err != nil {
		return "", err
	}
	// 等待任务结束
	wg.Wait()
	if stderrBuf.Len() > 0 {
		return stdoutBuf.String(), fmt.Errorf("%v", stderrBuf)
	}
	return stdoutBuf.String(), nil
}

func ConvertByte2String(byte []byte, charset Charset) string {
	var str string
	switch charset {
	case GB18030:
		var decodeBytes, _ = simplifiedchinese.GB18030.NewDecoder().Bytes(byte)
		str = string(decodeBytes)
	case GBK:
		var decodeBytes, _ = simplifiedchinese.GBK.NewDecoder().Bytes(byte)
		str = string(decodeBytes)
	case UTF8:
		fallthrough
	default:
		str = string(byte)
	}
	return str
}

var (
	localIP  string
	onceIP   sync.Once
	localMac string
	onceMac  sync.Once
)

// 获取本机第一张网卡的mac地址
func GetLocalMac() string {
	// 获取本机的MAC地址
	onceMac.Do(func() {
		interfaces, err := net.Interfaces()
		if err != nil {
			return
		}
		for _, inter := range interfaces {
			if inter.HardwareAddr == nil {
				continue
			}
			localMac = inter.HardwareAddr.String()
			break
		}
	})
	return localMac
}

// 获取本机ip
func GetIPv4() string {
	onceIP.Do(func() {
		addrs, err := net.InterfaceAddrs()
		if err != nil {
			return
		}
		for _, address := range addrs {
			// 检查ip地址判断是否回环地址
			if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
				if ipnet.IP.To4() != nil && ipnet.IP.String() != "127.0.0.1" {
					localIP = ipnet.IP.String()
					return
				}
			}
		}
	},
	)
	return localIP
}

func GetPosInfoSplit() string {
	_, file, line, ok := runtime.Caller(1)
	if !ok {
		return "[]||"
	}
	return "[" + filepath.Base(file) + ":" + strconv.Itoa(line) + "]||"
}

func GetBinDir() string {
	t, _ := filepath.Abs(os.Args[0])
	return strings.TrimRight(t, filepath.Base(t))
}
func GetBinDirNew() string {
	t, _ := os.Executable()
	return strings.TrimRight(strings.TrimRight(t, filepath.Base(t)), string(filepath.Separator))
}
func GetBinName() string {
	t, _ := os.Executable()
	return filepath.Base(t)
}
func GetPidByWindows(name string) (rets []int) {
	pids, _ := process.Pids()
	for _, pid := range pids {
		pn, _ := process.NewProcess(pid)
		pName, _ := pn.Name()
		if pName == name {
			rets = append(rets, int(pid))
		}
	}
	return rets
}
func PidByBinName(binName string) []int {
	binDir := GetBinDir()
	binDir = strings.TrimRight(binDir, string(filepath.Separator))
	//fmt.Printf("binDir:%v,binName:%v\n", binDir, binName)
	retSli := make([]int, 0, 5)
	//log.Warn("GetPid:", o.BinName)
	if runtime.GOOS == "windows" {
		return GetPidByWindows(binName)
	}
	cmdStr := fmt.Sprintf("pgrep %v -f", binName) // 进程名超过15个字符会查询失败，加-f参数可解决
	//cmdStr := fmt.Sprintf("pgrep %v", binName)
	result, err := ExecCmd(binDir, cmdStr)
	if err != nil {
		//log.Warnf("%v is not runing err:%v", binName, result)
		return retSli
	}
	if strings.HasSuffix(result, "\n") {
		result = strings.TrimRight(result, "\n")
	}
	pidSli := strings.Split(result, "\n")
	//log.Debugf("pidSli:%v", pidSli)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()
	for _, pidStr := range pidSli {
		cmdStr = fmt.Sprintf("ls -l /proc/%v | grep cwd | awk '{print $11}'", pidStr)
		runDir, err := ExecCmd2(ctx, binDir, cmdStr)
		if err != nil {
			log.Debug(err)
			continue
		}
		if strings.HasSuffix(runDir, "\n") {
			runDir = strings.TrimRight(runDir, "\n")
		}
		if runDir != binDir {
			//fmt.Printf("pidStr:%v runDir:%v  o.RunDir:%v\n", pidStr, runDir, binDir)
			log.Debugf("runDir:%v  o.RunDir:%v", runDir, binDir)
			continue
		}
		pid, _ := strconv.Atoi(pidStr)
		if pid <= 1 {
			log.Debugf("pid:%v", pid)
			continue
		}
		if pid == os.Getpid() {
			continue
		}
		retSli = append(retSli, pid)
	}
	return retSli
}

func MonitorStatus(interTime time.Duration, param ...func() string) {
	defer func() {
		if err := recover(); err != nil {
			zaplog.Warn("MonitorStatus err:", err)
		}
	}()
	tick := time.NewTicker(interTime)
	memorySize := 0.0
	mInfo := &process.MemoryInfoStat{}
	memStatus := &runtime.MemStats{}
	buf := bytes.Buffer{}
	var err error
	for {
		buf.Reset()
		select {
		case <-tick.C:
			mInfo, err = processObj.MemoryInfo()
			if err == nil {
				memorySize = float64(mInfo.RSS) / 1024.0 / 1024.0
			}
			runtime.ReadMemStats(memStatus)

			for _, v := range param {
				buf.WriteString(" " + v())
			}
			zaplog.Infof("current Goroutine:%-6d Memory:%.2fMb Heap:%.2fMb Stack:%.2fMb%v",
				runtime.NumGoroutine(),
				memorySize,
				float64(memStatus.HeapInuse)/1024.0/1024.0,
				float64(memStatus.StackInuse)/1024.0/1024.0,
				buf.String())
		}
	}
}
func printPprof(pprofPort string) {
	urlStr := "http://127.0.0.1:" + pprofPort + "/debug/pprof/goroutine?debug=1"
	req, err := http.NewRequest("GET", urlStr, nil)
	if err != nil {
		zaplog.Warn(err)
		return
	}
	req.Header.Add("Authorization", "Basic Z2FtZWFjY291bnQ6SElzSE9QRVJ2ZVJBbWF0aA==")
	resp, err := httpObj.Do(req)
	if err != nil {
		zaplog.Warn(err)
		return
	}
	defer resp.Body.Close()
	b, err := io.ReadAll(resp.Body)
	if err != nil {
		zaplog.Warn(err)
		return
	}
	zaplog.Warnf("printPprof[%v]:%v", urlStr, string(b))
}

// RunPprof addr ip:port
func RunPprof(addr string) {
	defer func() {
		if err := recover(); err != nil {
			zaplog.Warn("RunPprof err:", err)
		} else {
			zaplog.Info("RunPprof is stop")
		}
	}()
	if err := http.ListenAndServe(addr, nil); err != nil {
		zaplog.Warn(err)
	}
}

func GetBody(r *http.Request) []byte {
	b := []byte{}
	b, _ = ioutil.ReadAll(r.Body)
	return b
}
func FuncName() string {
	pc := make([]uintptr, 1)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	return filepath.Base(f.Name())
}

func TarGz(src string, dest string) error {
	// 打开文件夹
	dir, err := os.Open(src)
	if err != nil {
		return err
	}
	defer dir.Close()
	// file write
	fw, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer fw.Close()
	// gzip write
	gw := gzip.NewWriter(fw)
	defer gw.Close()
	// tar write
	tw := tar.NewWriter(gw)
	defer tw.Close()
	// 读取文件列表
	fis, err := dir.Readdir(0)
	if err != nil {
		return err
	}
	// 遍历文件列表
	for _, fi := range fis {
		// 逃过文件夹, 我这里就不递归了
		if fi.IsDir() {
			continue
		}
		// 打印文件名称
		//fmt.Println(fi.Name())
		// 打开文件
		fr, err := os.Open(dir.Name() + "/" + fi.Name())
		if err != nil {
			return err
		}
		defer fr.Close()
		// 信息头
		h := new(tar.Header)
		h.Name = fi.Name()
		h.Size = fi.Size()
		h.Mode = int64(fi.Mode())
		h.ModTime = fi.ModTime()
		// 写信息头
		err = tw.WriteHeader(h)
		if err != nil {
			return err
		}
		// 写文件
		_, err = io.Copy(tw, fr)
		if err != nil {
			return err
		}
	}
	return nil
}

func IPUint32ToIP(intIP uint32) net.IP {
	return net.IPv4(byte((intIP>>24)&0xFF), byte((intIP>>16)&0xFF), byte((intIP>>8)&0xFF), byte(intIP&0xFF))
}

func IPIPToUint32(ipnr net.IP) (sum uint32, err error) {
	return IPStringToUint32(ipnr.String())
}

func IPStringToUint32(ipString string) (sum uint32, err error) {
	bits := strings.Split(ipString, ".")
	if len(bits) != 4 {
		return sum, errors.New("ip format err")
	}
	b0, _ := strconv.Atoi(bits[0])
	b1, _ := strconv.Atoi(bits[1])
	b2, _ := strconv.Atoi(bits[2])
	b3, _ := strconv.Atoi(bits[3])

	sum += uint32(b0) << 24
	sum += uint32(b1) << 16
	sum += uint32(b2) << 8
	sum += uint32(b3)
	return
}

func HideStar(str string) (result string) {
	if str == "" {
		return "***"
	}
	nameRune := []rune(str)
	// 邮箱
	if strings.Contains(str, "@") {
		res := strings.Split(str, "@")
		if len(res[0]) < 4 {
			result = "***@" + res[1]
		} else {
			result = string(nameRune[:3]) + "***@" + res[1]
		}
		return result
	}
	reg := `^[0-9]\d{9}$`
	rgx := regexp.MustCompile(reg)
	mobileMatch := rgx.MatchString(str)
	// 手机号
	if mobileMatch {
		return string(nameRune[:3]) + "***" + string(nameRune[7:11])
	}
	// 普通字符串
	lens := len(nameRune)
	if lens < 4 {
		result = "***"
	} else if lens < 9 {
		result = string(nameRune[:2]) + "***" + string(nameRune[lens-2:])
	} else {
		result = string(nameRune[:4]) + "***" + string(nameRune[lens-4:])
	}
	return result
}

//func Substr2(str string, start int, end int) string {
//	rs := []rune(str)
//	return string(rs[start:end])
//}

func PrintEncode(str string) (newStr string) {
	n := len(str)
	if n <= 3 {
		return str
	} else {
		return str[:4] + "***" + str[n-4:]
	}
}
func Zip(zipPath string, paths ...string) error {
	// create zip file
	if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
		return err
	}
	archive, err := os.Create(zipPath)
	if err != nil {
		return err
	}
	defer archive.Close()

	// new zip writer
	zipWriter := zip.NewWriter(archive)
	defer zipWriter.Close()

	// traverse the file or directory
	for _, srcPath := range paths {
		// remove the trailing path separator if path is a directory
		srcPath = strings.TrimSuffix(srcPath, string(os.PathSeparator))

		// visit all the files or directories in the tree
		err = filepath.Walk(srcPath, func(path string, info fs.FileInfo, err error) error {
			if err != nil {
				return err
			}

			// create a local file header
			header, err := zip.FileInfoHeader(info)
			if err != nil {
				return err
			}

			// set compression
			header.Method = zip.Deflate

			// set relative path of a file as the header name
			header.Name, err = filepath.Rel(filepath.Dir(srcPath), path)
			if err != nil {
				return err
			}
			if info.IsDir() {
				header.Name += string(os.PathSeparator)
			}

			// create writer for the file header and save content of the file
			headerWriter, err := zipWriter.CreateHeader(header)
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}
			f, err := os.Open(path)
			if err != nil {
				return err
			}
			defer f.Close()
			_, err = io.Copy(headerWriter, f)
			return err
		})
		if err != nil {
			return err
		}
	}
	return nil
}

// Unzip decompresses a zip file to specified directory.
// Note that the destination directory don't need to specify the trailing path separator.
func Unzip(zipPath, dstDir string) error {
	// open zip file
	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		return err
	}
	defer reader.Close()
	for _, file := range reader.File {
		if err := unzipFile(file, dstDir); err != nil {
			return err
		}
	}
	return nil
}

func unzipFile(file *zip.File, dstDir string) error {
	// create the directory of file
	filePath := path.Join(dstDir, file.Name)
	if file.FileInfo().IsDir() {
		if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
			return err
		}
		return nil
	}
	if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
		return err
	}

	// open the file
	r, err := file.Open()
	if err != nil {
		return err
	}
	defer r.Close()

	// create the file
	w, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer w.Close()

	// save the decompressed file content
	_, err = io.Copy(w, r)
	return err
}
